package com.gitee.yusugar.core.task;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.gitee.yusugar.common.YuToolServiceException;
import com.gitee.yusugar.core.task.enums.TimerStrType;
import com.gitee.yusugar.core.task.pojo.TimerInfo;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 表达式生成类
 * <p>
 * 1. 根据年月日生成的格式：2023 1,5,9,10 12-15 8 13,30,40 25,30 ?
 * 注意：最后一个“?”号代表周，当指定周为“?”号的时候，周为无效，此时"?"号也可省略
 * 2. 根据周生成格式：* * * 10-12 20-55 10,20,30,50 1-3
 * 注意：当指定周后，年月日当设置为“*”，就算设置为其它数值，年月日也不会生效
 *
 * @author Y.sugar
 * @since 2022/12/14 14:14
 */
public class TimerGenerator {

    // 空字符，用于分割 年 月 日 时 分 秒 周。
    private static final String EMPTY = " ";

    // 连接字符，用于取连贯日期 例如 1日到15日，可写 1-15
    private static final String JOIN = "-";

    // 分割字符，用于取不连贯日期 例如 1日和15日，可写 1,15
    private static final String SPLIT = ",";

    // 时间分割字符
    private static final String TIME_SPLIT = ":";

    // 该时间的所有时间
    private static final String ALL_DATE = "*";

    // 未知的周
    private static final String WEEK_NONE = "?";

    // 用于验证用户输入的日期是否合法
    private static final String REGEX = "(\\d{1,4})-(\\d{1,4})";

    /**
     * 将表达式转换成日期对象
     *
     * @param expression 表达式
     * @return 日期对象
     */
    public static TimerInfo convert(String expression) {
        TimerInfo timerInfo = new TimerInfo();

        String[] splitDateStrArr = expression.split(EMPTY);
        for (int i = 0; i < splitDateStrArr.length; i++) {
            TimerStrType timerStrType = TimerStrType.find(i);
            if (timerStrType == null) {
                throw new YuToolServiceException("【DateTypeEnum.class】未知的值：" + splitDateStrArr[i]);
            }

            timerInfo.setVal(judgmentRangeAndParseDate(timerStrType, splitDateStrArr[i]), i);
        }

        return timerInfo;
    }

    public static void main(String[] args) {
        System.out.println(createFillZero("* * * 10-12 20-55 10,20,30,50 1-3"));
        System.out.println(createFillZero("2023 1,5,9,10 12-15 8 13,30,40 25,30"));
    }


    /**
     * 生成任务的下次执行日期，并将不足两位的日期补0
     *
     * @param expression 表达式
     * @return 生成的时间
     */
    public static String createFillZero(String expression) {
        return createFillZero(convert(expression));
    }

    /**
     * 生成任务的下次执行日期，并将不足两位的日期补0
     *
     * @param expression 表达式
     * @param nowDateStr 当前日期字符串
     * @return 生成的时间
     */
    public static String createFillZero(String expression, String nowDateStr) {
        return createFillZero(convert(expression), nowDateStr);
    }

    /**
     * 生成任务的下次执行日期
     *
     * @param expression 表达式
     * @return 生成的时间
     */
    public static String create(String expression) {
        return create(convert(expression));
    }

    /**
     * 生成任务的下次执行日期
     *
     * @param expression 表达式
     * @param nowDateStr 当前日期字符串
     * @return 生成的时间
     */
    public static String create(String expression, String nowDateStr) {
        return create(convert(expression), nowDateStr);
    }

    /**
     * 生成任务的下次执行日期，并将不足两位的日期补0
     *
     * @param timerInfo 定时日期对象
     * @return 生成的时间
     */
    public static String createFillZero(TimerInfo timerInfo) {
        return createFillZero(timerInfo, DateUtil.now());
    }

    /**
     * 生成任务的下次执行日期，并将不足两位的日期补0
     *
     * @param timerInfo  定时日期对象
     * @param nowDateStr 当前日期字符串
     * @return 生成的时间
     */
    public static String createFillZero(TimerInfo timerInfo, String nowDateStr) {
        return DateUtil.parseDateTime(create(timerInfo, nowDateStr)).toString();
    }

    /**
     * 生成任务的下次执行日期
     *
     * @param timerInfo 定时日期对象
     * @return 生成的时间
     */
    public static String create(TimerInfo timerInfo) {
        return create(timerInfo, DateUtil.now());
    }

    /**
     * 生成任务的下次执行日期
     *
     * @param timerInfo  定时日期对象
     * @param nowDateStr 当前日期字符串
     * @return 生成的时间
     */
    public static String create(TimerInfo timerInfo, String nowDateStr) {
        // 获取当前时间
        DateTime nowDate = DateUtil.parseDateTime(nowDateStr);
        int thisYear = nowDate.getField(DateField.YEAR);
        int thisMonth = nowDate.getField(DateField.MONTH) + 1;
        int thisDay = nowDate.getField(DateField.DAY_OF_MONTH);
        int thisHour = nowDate.getField(DateField.HOUR_OF_DAY);
        int thisMinute = nowDate.getField(DateField.MINUTE);
        int thisSecond = nowDate.getField(DateField.SECOND);
        int thisWeek = nowDate.getField(DateField.DAY_OF_WEEK) - 1;
        if (thisWeek == 0) {
            thisWeek = 7;
        }

        List<Integer> years = timerInfo.getYears();
        List<Integer> months = timerInfo.getMonths();
        List<Integer> days = timerInfo.getDays();
        List<Integer> hours = timerInfo.getHours();
        List<Integer> minutes = timerInfo.getMinutes();
        List<Integer> seconds = timerInfo.getSeconds();
        List<Integer> weeks = timerInfo.getWeeks();

        // 获取是否不是是周计划，若是周计划，则年月日日期都无用
        boolean isNotWeekPlan = weeks == null || weeks.isEmpty();

        // 构建日期字符串数组
        Integer[] dateTime = new Integer[6];

        // restVal：用于判断是否重置，当下一分钟无法执行的时候，秒数会被设置为 30，但其实应以第一个为准
        int restVal = 0;
        // 获取秒数
        restVal = findDateTime(dateTime, seconds, true, thisSecond, restVal, 0);
        // 获取分钟
        restVal = findDateTime(dateTime, minutes, dateTime[0] != null, thisMinute, restVal, 1);
        // 获取小时
        restVal = findDateTime(dateTime, hours, dateTime[1] != null, thisHour, restVal, 2);
        if (isNotWeekPlan) {
            // 获取天
            restVal = findDateTime(dateTime, days, dateTime[2] != null, thisDay, restVal, 3);
            // 获取月
            restVal = findDateTime(dateTime, months, dateTime[3] != null, thisMonth, restVal, 4);
            // 获取年
            restVal = findDateTime(dateTime, years, dateTime[4] != null, thisYear, restVal, 5);
        } else {

            // 查找 >= 或 > 当前周
            int finalWeek = thisWeek;
            Integer nextWeek = weeks.stream().filter(date -> dateTime[2] != null ? date >= finalWeek : date > finalWeek).findFirst().orElse(null);

            int offsetDay = 0;
            // 若未找到，则从头开始，将 时分秒 重置
            if (nextWeek == null) {
                restVal = 3;

                // 计算出需要往后延迟几天
                offsetDay = 7 - thisWeek + weeks.get(0);
            } else {
                // 否者就是这周还可以执行，计算出距离下次执行还剩几天，就将日期往后移几天
                if (nextWeek != finalWeek) {
                    offsetDay = nextWeek - finalWeek;
                }
            }

            // 当 offsetDay 不为0时再延迟天数
            if (offsetDay != 0) {
                nowDate = DateUtil.offsetDay(nowDate, offsetDay);
            }
            dateTime[5] = nowDate.getField(DateField.YEAR);
            dateTime[4] = nowDate.getField(DateField.MONTH) + 1;
            dateTime[3] = nowDate.getField(DateField.DAY_OF_MONTH);
        }

        // 根据要重置的位数，重置之前的时间
        switch (restVal) {
            case 6:
                dateTime[5] = years.get(0);
            case 5:
                dateTime[4] = months.get(0);
            case 4:
                dateTime[3] = days.get(0);
            case 3:
                dateTime[2] = hours.get(0);
            case 2:
                dateTime[1] = minutes.get(0);
            case 1:
                dateTime[0] = seconds.get(0);
                break;
        }

        return joinDateStr(dateTime);
    }

    private static int findDateTime(Integer[] nextDateTime, List<Integer> dateTimeList, boolean lastObtainFlag, int thisDateTime, int restVal, int position) {
        // 查找 >= 或 > 当当前的日期，为true则代表当前能执行，为false则代表需要到下一次执行
        boolean nowObtainFlag = (nextDateTime[position] = dateTimeList.stream().filter(date -> lastObtainFlag ? date >= thisDateTime : date > thisDateTime).findFirst().orElse(null)) != null;
        // 这代表要到下次执行，则重置之前的时间
        if (nowObtainFlag && nextDateTime[position] > thisDateTime) {
            restVal = position;
        }
        // 代表未找到符合的时间，下次执行
        if (!nowObtainFlag) {
            restVal = (position + 1);
        }

        return restVal;
    }

    private static String joinDateStr(Integer[] dateTime) {
        return dateTime[5] + JOIN + dateTime[4] + JOIN + dateTime[3] + EMPTY + dateTime[2] + TIME_SPLIT + dateTime[1] + TIME_SPLIT + dateTime[0];
    }

    /**
     * 解析用户输入的日期字符串，并验证
     *
     * @param timerStrType 待解析的日期类别
     * @param dateVal      日期字符串
     * @return 解析后的日期集合
     */
    private static List<Integer> judgmentRangeAndParseDate(TimerStrType timerStrType, String dateVal) {
        // 创建日期存储集合
        List<Integer> dateValList = new ArrayList<>();
        // 获取范围
        Integer min = timerStrType.min;
        Integer max = timerStrType.max;

        // 若当前为“周”，且未填值
        if (WEEK_NONE.equals(dateVal) && timerStrType.type.equals(6)) {
            return dateValList;
        } else if (ALL_DATE.equals(dateVal)) {
        } else {
            // 根据固定格式分割日期
            String[] dateValArr = dateVal.split(SPLIT);
            for (String dateValC : dateValArr) {
                // 判断是否包含连接符
                if (dateValC.contains(JOIN)) {
                    // 判断是否匹配固定格式
                    if (!dateValC.matches(REGEX)) {
                        throw new YuToolServiceException("格式异常：" + dateValC);
                    }
                    String[] dateValCArr = dateValC.split(JOIN);

                    // 获取开始和结束值，并判断开始值是否大于结束值，若大于，则调换两者的值
                    int startVal = Integer.parseInt(dateValCArr[0]);
                    int endVal = Integer.parseInt(dateValCArr[1]);
                    if (startVal > endVal) {
                        startVal = startVal ^ endVal;
                        endVal = startVal ^ endVal;
                        startVal = startVal ^ endVal;
                    }

                    // 判断开始值是否小于最小值
                    if (startVal < min)
                        startVal = min;

                    // 判断结束值是否大于最大值
                    if (endVal > max)
                        endVal = max;

                    // 从开始值遍历，依次添加
                    while (startVal <= endVal) {
                        dateValList.add(startVal++);
                    }
                } else {
                    if (StrUtil.isNotBlank(dateValC)) {
                        // 判断该值是否超过了最大值，超过了就忽略，不超过就添加
                        int val = Integer.parseInt(dateValC);
                        if (val >= min && val <= max)
                            dateValList.add(val);
                    }
                }
            }
        }

        if (dateValList.isEmpty()) {
            // 判断 日期列表是否为空，若为空，则把当前类型的全部都加进去
            for (int j = timerStrType.min; j <= timerStrType.max; j++) {
                dateValList.add(j);
            }
        }

        return dateValList.stream().distinct().sorted().collect(Collectors.toList());
    }
}
